<?php

namespace App\Services\Bolai;

use App\Model\Normal;
use Hyperf\Config\Annotation\Value;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\ApplicationContext;
use Swoole\Server;
use Exception;

class KV {
    private array $_key;
    private array $_value;
    private bool $_isStringKey;

    public function __construct(mixed $key, mixed $value,int $byteLength = null) {
        $this->_key = $this->ensureBuffer($key);
        $this->_value = $this->ensureBuffer($value,$byteLength);
        $this->_isStringKey = $this->isString($key);
        $this->_checkKey($key);
    }

    private function _checkKey(mixed $key): void {
        if (!$this->isString($key) && !$this->isNumber($key)) {
            throw new Exception("Key is not string or number");
        }
    }

    private function isString(mixed $v): bool {
        return is_string($v);
    }

    private function isNumber(mixed $v): bool {
        return is_int($v);
    }

    private function ensureBuffer(mixed $v, ?int $byteLength = null): array
    {
        if (is_string($v)) {
            return $this->stringToBuffer($v);
        }

        if (is_int($v)) {
            return $this->encodeNumber($v, $byteLength);
        }

        if (is_array($v)) {
            return $v;
        }

        throw new Exception("Invalid value, cannot be converted to buffer");
    }

    private function encodeNumber(int $v, ?int $byteLength = null): array {
        $b = [];
        while ($v > 0) {
            $b[] = $v & 0xFF; // 取最低 8 位
            $v >>= 8; // 右移 8 位
        }

        // 如果值为 0，确保至少有一个字节
        if (empty($b)) {
            $b = [0];
        }

        // 如果指定了字节长度，则填充或截断
        if ($byteLength !== null) {
            if (count($b) > $byteLength) {
                $b = array_slice($b, 0, $byteLength); // 截断
            } elseif (count($b) < $byteLength) {
                $b = array_pad($b, $byteLength, 0); // 填充
            }
        }

        return array_reverse($b); // 高位在前
    }

//    private function encodeNumber(int $v,?int $byteLength = null): array {
//        if ($v === 0) {
//            return [0];
//        }
//
//        $b = [];
//        while ($v > 0) {
//            $b[] = $v & 0xFF;
//            $v >>= 8;
//        }
//
//        // 如果指定了字节长度，则填充或截断
//        if ($byteLength !== null) {
//            if (count($b) > $byteLength) {
//                $b = array_slice($b, 0, $byteLength); // 截断
//            } elseif (count($b) < $byteLength) {
//                $b = array_pad($b, $byteLength, 0); // 填充
//            }
//        }
//
//        return array_reverse($b); // 高位在前
//    }

    public static function decodeNumber(array $v): int {
        $n = 0;
        if (count($v) > 8) {
            $v = array_slice($v, 0, 8);
        }

        foreach ($v as $b) {
            $n <<= 8;
            $n |= $b;
        }

        return $n;
    }

    private function encodeLength(int $l): array {
        $b = array_fill(0, 8, 0);
        $i = 0;
        while ($l > 0) {
            $b[$i] = ($l & 0x7F) | 0x80;
            $l >>= 7;
            $i++;
            if ($i > 8) {
                throw new Exception("Invalid length");
            }
        }
        $la = array_slice($b, 0, $i);
        $la = array_reverse($la);
        $lastByte = $la[$i - 1];
        $lastByte &= 0x7F;
        $la[$i - 1] = $lastByte;
        return $la;
    }

    public static function decodeLength(array $buffer): array {
        $la = array_fill(0, 8, 0);
        $lengthByteSize = 0;
        for ($i = 0; $i < count($buffer); $i++) {
            $b = $buffer[$i];
            $la[$i] = $b & 0x7F;
            $lengthByteSize++;
            if (($b & 0x80) === 0) {
                break;
            }
        }
        if ($lengthByteSize === 0 || $lengthByteSize > 4) {
            return ['code' => 1, 'length' => 0, 'lengthByteSize' => 0];
        }

        $length = 0;
        for ($i = 0; $i < $lengthByteSize; $i++) {
            $length <<= 7;
            $length |= $la[$i];
        }

        return [
            'code' => 0,
            'length' => $length,
            'lengthByteSize' => $lengthByteSize
        ];
    }

    private function stringToBuffer(string $content): array {
        $buf = [];
        for ($i = 0; $i < strlen($content); $i++) {
            $buf[] = ord($content[$i]);
        }
        return $buf;
    }

    public static function bufferToString(array $buffer): string {
        $content = '';
        foreach ($buffer as $b) {
            $content .= chr($b);
        }
        return $content;
    }

    public static function bufferToHex(array $buffer): string {
        $hex = '';
        foreach ($buffer as $b) {
            $hex .= sprintf("%02x", $b);
        }
        return $hex;
    }

    public function pack(): array {
        $keyLength = count($this->_key);
        $totalLength = 1 + $keyLength + count($this->_value);
        $lengthBuffer = $this->encodeLength($totalLength);
        $lengthBufferSize = count($lengthBuffer);
        $finalLength = $lengthBufferSize + $totalLength;

        $keyLengthByte = $keyLength & 0x7F;
        if ($this->_isStringKey) {
            $keyLengthByte |= 0x80;
        }

        $buffer = array_fill(0, $finalLength, 0);
        array_splice($buffer, 0, $lengthBufferSize, $lengthBuffer);
        $buffer[$lengthBufferSize] = $keyLengthByte;
        array_splice($buffer, $lengthBufferSize + 1, $keyLength, $this->_key);
        array_splice($buffer, $lengthBufferSize + 1 + $keyLength, count($this->_value), $this->_value);

        return $buffer;
    }

    public static function unpack(array $buffer): array {
        if (!$buffer || count($buffer) === 0) {
            return ['code' => 1, 'kv' => null, 'pendingParseBuffer' => $buffer];
        }

        $dlr = self::decodeLength($buffer);
        if ($dlr['code'] !== 0) {
            return ['code' => -1, 'kv' => null, 'pendingParseBuffer' => null];
        }

        $payloadLength = $dlr['length'];
        $remainingLength = count($buffer) - $dlr['lengthByteSize'] - $payloadLength;
        if ($remainingLength < 0 || (count($buffer) - $dlr['lengthByteSize']) < 0) {
            return ['code' => -2, 'kv' => null, 'pendingParseBuffer' => $buffer];
        }

        $payload = array_slice($buffer, $dlr['lengthByteSize'], $dlr['length']);
        if (count($payload) === 0) {
            return ['code' => -2, 'kv' => null, 'pendingParseBuffer' => $buffer];
        }

        $isStringKey = false;
        $keySizeByte = $payload[0];
        $keyLength = $keySizeByte & 0x7F;
        if (($keySizeByte & 0x80) !== 0) {
            $isStringKey = true;
        }

        $valueLength = count($payload) - 1 - $keyLength;
        if ($valueLength < 0) {
            return ['code' => -3, 'kv' => null, 'pendingParseBuffer' => $buffer];
        }

        $keyBuffer = array_slice($payload, 1, $keyLength);
        $key = $isStringKey ? self::bufferToString($keyBuffer) : self::decodeNumber($keyBuffer);
        $valueBuffer = array_slice($payload, 1 + $keyLength);
        $kv = new KV($key, $valueBuffer);

        return [
            'code' => 0,
            'kv' => $kv,
            'pendingParseBuffer' => array_slice($buffer, $dlr['lengthByteSize'] + $dlr['length'])
        ];
    }

    public function isStringKey(): bool {
        return $this->_isStringKey;
    }

    public function key(): string|int {
        return $this->_isStringKey ? $this->bufferToString($this->_key) : $this->decodeNumber($this->_key);
    }

    public function value(): array {
        return $this->_value;
    }

    public function stringValue(): string {
        return $this->bufferToString($this->_value);
    }

    public function numberValue(): int {
        return $this->decodeNumber($this->_value);
    }
}

